package edu.cpcc.blackboard.authentication;

import blackboard.platform.security.authentication.BaseAuthenticationModule;
import blackboard.platform.security.authentication.BbAuthenticationFailedException;
import blackboard.platform.security.authentication.BbCredentialsNotFoundException;
import blackboard.platform.security.authentication.BbSecurityException;
import blackboard.platform.security.authentication.HttpAuthConfig;
import blackboard.platform.config.ConfigurationService;
import blackboard.data.user.User;
import blackboard.persist.user.UserDbLoader;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;


public class CasAuthenticationModule extends BaseAuthenticationModule
{
    private static String[] CAS_PROP_KEYS = new String[]
    {
        "url",
        "redirect.url",
        "isdefault",
        "service",
        "default_loc",
        "logout_completely",
        "use_post",
        "impl",
        "exclude_home",
        "lookup_user_in_BB"     
    };

    // URL to CAS server
    private static String casUrl;
    // URL for service sent to CAS - return url
    private static String casService;
    // URL to redirect logouts to
    private static String casRedirectUrl;
    // URL for default url on success - if new_loc not specified
    private static String defaultLoc;

    private static boolean casLogoutCompletely = true;
    private static boolean casIsDefaultAuth = false;
    private static boolean casUsePost = false;
    private static boolean casExcludeHome = true;
    private static boolean casLookupUserInBB = true;


    // From interface - so it knows which keys under auth.type.cas to read
    public String[] getPropKeys()
    {
        return CAS_PROP_KEYS;
    }

    public void setConfig(HttpAuthConfig config)
    {
        _config = config;
    }

    public void init(ConfigurationService cfg)
    {
        casUrl = getProperty("url");
        casService = getProperty("service");
        casRedirectUrl = getProperty("redirect.url");
        defaultLoc = getProperty("default_loc");

        if ("true".equals(getProperty("isdefault")))
            casIsDefaultAuth = true;

        if ("true".equals(getProperty("use_post")))
            casUsePost = true;

        if ("false".equals(getProperty("logout_completely")))
            casLogoutCompletely = false;

        if ("false".equals(getProperty("exclude_home")))
            casExcludeHome = false;
        
        if ("false".equals(getProperty("lookup_user_in_BB")))
            casLookupUserInBB = false;
    }

    private String getProperty(String key)
    {
        // Blackboard 'maps' property values to 'appropriate' object types
        // e.g. http://etc -> URL
        // true/false -> Boolean
        Object val = _config.getProperty(key);
        if (val == null)
            return null;
        else
            return val.toString();
    }


    // Given username, returns user object from database
    // If user not in database, returns null
    private User getDBUserByUserName(String userName)
    {
        User user;

        try 
        {
            user = UserDbLoader.Default.getInstance().loadByUserName(userName);
        }
        catch (Exception e) 
        {
            return null;
        }

        return user;
    }


    // If CAS IS NOT the default it WILL be used IF new_loc parameter
    // contains 'useCas'.
    //
    // If CAS IS the default it WILL NOT be used if new_loc parameter
    // contains 'notCas'.
    public String doAuthenticate(HttpServletRequest request,
                                 HttpServletResponse response)
        throws BbSecurityException, BbAuthenticationFailedException,
               BbCredentialsNotFoundException
    {


        // new_loc -> url to redirect to after successful login
        String newLoc = request.getParameter("new_loc");

        if ((!casIsDefaultAuth && (newLoc == null || newLoc.indexOf("useCas") == -1))
            ||
            (casIsDefaultAuth && newLoc != null && newLoc.indexOf("notCas") != -1)
            ) 
        {
            // Original Blackboard authentication
            return super.doAuthenticate(request,response);
        }

        // Authenticate when on Home/Portal page?
        String contextPath = request.getContextPath();   
        if (casExcludeHome && contextPath.equals(""))
        {
                // Don't require authentication on site root
                // Original Blackboard authentication 
                super.requestAuthenticate(request,response);
                return null;
        }


        // CAS validation of service
        String ticket = request.getParameter("ticket");

        // can't validate without a ticket
        if (ticket == null || ticket.equals(""))
            return null;

        // using CAS client library
        Cas20ServiceTicketValidator stv = new Cas20ServiceTicketValidator(casUrl);
        String uid = null;

        String service = casService;
        if (newLoc != null && newLoc.length() != 0)
            service = service + "?new_loc=" +
                      java.net.URLEncoder.encode(newLoc);

        try
        {
            uid = stv.validate(ticket, service).getPrincipal().getName();
        }
        catch (Exception e)
        {
            throw new BbAuthenticationFailedException(e.getMessage());
        }

        // errMsg does not seem to be getting set automatically in BB9 
        if (casLookupUserInBB)
        {
            // manually look up user in DB
            if (getDBUserByUserName(uid) != null)
            {
                // user exists in db
                request.setAttribute("userInDB", "true");
            }
            else
            {
                request.setAttribute("userInDB", "false");         
            }
        }

        return uid;
    }

    public void doLogout(javax.servlet.http.HttpServletRequest request,
                         javax.servlet.http.HttpServletResponse response)
        throws BbSecurityException
    {
        super.doLogout(request, response);

        if (!casLogoutCompletely)
            return;

        try
        {
            String redirectUrl = casUrl + "/logout";
            if (casRedirectUrl != null && casRedirectUrl.length() != 0)
                redirectUrl = redirectUrl + "?url=" +
                              java.net.URLEncoder.encode(casRedirectUrl);

            response.sendRedirect(redirectUrl);
        }
        catch (Exception e)
        {
            throw new BbSecurityException(" Can't reach logout page: " +
                                          e.getMessage());
        }
    }

    // If CAS IS NOT the default it WILL be used IF new_loc parameter
    // contains 'useCas'.
    //
    // If CAS IS the default it WILL NOT be used if new_loc parameter
    // contains 'notCas'.
    //
    // Normally you request a URL. If you are not authenticated you are sent
    // to /webapps/login?new_loc=[original destination].
    public void requestAuthenticate(HttpServletRequest request,
                                    HttpServletResponse response)
        throws BbSecurityException
    {
        String service = casService;
        String newLoc = request.getParameter("new_loc");

        if ((!casIsDefaultAuth &&
             (newLoc == null || newLoc.indexOf("useCas") == -1))
            ||
            (casIsDefaultAuth &&
             newLoc != null && newLoc.indexOf("notCas") != -1)
            )
        {
            super.requestAuthenticate(request, response);
            return;
        }

        // If we're on home page, don't need authentication
        String contextPath = request.getContextPath();   
        if (casExcludeHome && contextPath.equals(""))
            {
                return;
            }


        // Handle User not being in DB

        // check for error messages
        String errMsg = (String) request.getAttribute("msg");

        // check if error message needs to be set manually, due to user not in db
        String userInDB = (String) request.getAttribute("userInDB");
        if ((errMsg == null) && (userInDB != null) && ("false".equals(userInDB)))
            errMsg = "Unable to retrieve user record from the database.";

        if (errMsg != null && errMsg.indexOf("Unable to retrieve user " +
            "record from the database") != -1) 
        {
            try
            {
                response.sendError(401, errMsg);
            }
            catch (Exception e)
            {
                throw new BbSecurityException(e.getMessage());
            }
            return;
        }

        // Prepare for redirecting to CAS
        if (newLoc != null && newLoc.length() != 0)
            newLoc = java.net.URLEncoder.encode(newLoc);
        else
            newLoc = java.net.URLEncoder.encode(defaultLoc);

        String casPath = "/login?";
        if (casUsePost)
        {
            casPath = casPath + "method=POST&";
            service = "service=" + java.net.URLEncoder.encode(service) +
                      "&new_loc=" + newLoc;
        }
        else
        {
            service = "service=" + java.net.URLEncoder.encode(service +
                      "?new_loc=" + newLoc);
        }

        // Redirect to CAS
        try
        {
            response.sendRedirect(casUrl + casPath + service);
        }
        catch (Exception e)
        {
            throw new BbSecurityException(" Can't reach login page: " +
                                          e.getMessage());
        }
    }


    public String getAuthType()
    {
        return "cas";
    }

    public static void main(String[] args) throws Exception
    {
        if (args.length != 2)
            throw new Exception("You must supply 2 arguments - CAS " +
                                "service validate url and the service");

        Cas20ServiceTicketValidator stv = new Cas20ServiceTicketValidator(
            args[0]);
        stv.validate("foo", java.net.URLEncoder.encode(args[1]));
    }
}
